package com.example.fuluchou.service.mq;

import cn.hutool.core.date.DateUtil;
import com.example.fuluchou.common.exception.ServiceException;
import com.example.fuluchou.common.untils.JacksonUtil;
import com.example.fuluchou.common.untils.MailUtil;
import com.example.fuluchou.common.untils.SMSUtil;
import com.example.fuluchou.controller.param.DrawPrizeParam;
import com.example.fuluchou.dao.dataobject.ActivityPrizeDO;
import com.example.fuluchou.dao.dataobject.WinningRecordDO;
import com.example.fuluchou.dao.mapper.ActivityPrizeMapper;
import com.example.fuluchou.dao.mapper.WinningRecordMapper;
import com.example.fuluchou.service.DrawPrizeService;
import com.example.fuluchou.service.activitystatus.ActivityStatusManager;
import com.example.fuluchou.service.dto.ConvertActivityStatusDTO;
import com.example.fuluchou.service.enums.ActivityPrizeStatusEnum;
import com.example.fuluchou.service.enums.ActivityPrizeTiersEnum;
import com.example.fuluchou.service.enums.ActivityStatusEnum;
import com.example.fuluchou.service.enums.ActivityUserStatusEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.example.fuluchou.common.config.DirectRabbitConfig.QUEUE_NAME;

@Component
@RabbitListener(queues = QUEUE_NAME)
public class MqReceiver {
    private static final Logger logger = LoggerFactory.getLogger(MqReceiver.class);

    @Autowired
    private DrawPrizeService drawPrizeService;
    @Autowired
    private ActivityStatusManager activityStatusManager;
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    @Autowired
    private MailUtil mailUtil;
    @Autowired
    private SMSUtil smsUtil;
    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;
    @Autowired
    private WinningRecordMapper winningRecordMapper;


    @RabbitHandler
    public void process(Map<String,String> message) throws Exception {
        //成功接收到队列中的消息
        logger.info("MQ成功接收到消息，message:{}",
                JacksonUtil.writeValueAsString(message));
        String paramString = message.get("messageData");
        DrawPrizeParam param = JacksonUtil.readValue(paramString,DrawPrizeParam.class);

        //处理抽奖的流程
        try {

            //下面四步需要保持一致性，当任何一步异常时，需进行回滚
            //校验抽奖请求是否有效
           if(!drawPrizeService.checkDrawPrizeParam(param)) {
               return;
            }

            //状态扭转处理(涉及设计模式,责任链模式、策略模式)
            statusConvert(param);

            //保存中奖者名单
            List<WinningRecordDO> winningRecordDOList =
                    drawPrizeService.saveWinnerRecords(param);

            //根据中奖信息,通知中奖者（包括邮箱、短信）异步处理
            syncExecute(winningRecordDOList);

        } catch (ServiceException e){
            logger.info("处理 MQ 消息异常! {}:{}",e.getCode(),e.getMessage(),e);
            //如果异常,需要保持事务一致性(回滚)
            rollback(param);
            // 抛出异常: 消息重试（解决异常：代码bug、网络问题、服务问题）
            throw e;

        } catch (Exception e){
            logger.info("处理 MQ 消息异常!",e);
            //如果异常,需要保持事务一致性(回滚)
            rollback(param);
            // 抛出异常
            throw e;
        }

    }

    /**
     * 处理抽奖异常的回滚行为
     * 恢复到处理数据之前的库表状态
     *
     * @param param
     */
    private void rollback(DrawPrizeParam param) {

        //1.回滚状态：人员、活动、奖品的状态
        //判断状态来确定是否需要回滚
        if(!statusNeedRollback(param)){
            //不需要：return
            return;
        }
        //需要：回滚
        rollbackStatus(param);
        //2.回滚中奖者名单
        //是否需要回滚
        if(!winnerNeedRollback(param)){
            //不需要
            return;
        }
        //需要：回滚
        rollbackWinner(param);
    }

    /**
     * 回滚中奖记录：删除奖品下的中奖者
     *
     * @param param
     */
    private void rollbackWinner(DrawPrizeParam param) {
        drawPrizeService.deleteRecords(param.getActivityId(),param.getPrizeId());
    }

    private boolean winnerNeedRollback(DrawPrizeParam param) {
        int count = winningRecordMapper.countByAPId(param.getActivityId(),param.getPrizeId());
        return count > 0;
    }

    private void rollbackStatus(DrawPrizeParam param) {
        //涉及状态的回滚，使用ActivityStatusManager
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActicityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.RUNNING);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.INIT);
        convertActivityStatusDTO.setUserIds(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.INIT);

        activityStatusManager.rollbackHandlerEvent(convertActivityStatusDTO);
    }

    private boolean statusNeedRollback(DrawPrizeParam param) {
        // 判断奖品状态是否扭转，就能判断出全部状态是否扭转
        ActivityPrizeDO activityPrizeDO = activityPrizeMapper.selectByAPId(param.getActivityId(),param.getPrizeId());
        return activityPrizeDO.getStatus()
                .equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETED.name());
    }

    /**
     * 并发处理通知流程，采用线程池进行异步处理
     * @param winningRecordDOList
     */
    private void syncExecute(List<WinningRecordDO> winningRecordDOList) {
        //使用 线程池threadPoolTaskExecutor 进行异步任务处理
        //短信通知
        threadPoolTaskExecutor.execute(()->sendMessage(winningRecordDOList));
        //邮箱通知
        threadPoolTaskExecutor.execute(()->sendMail(winningRecordDOList));

    }

    /**
     * 发邮件
     * @param winningRecordDOList
     */
    private void sendMail(List<WinningRecordDO> winningRecordDOList) {
        if(CollectionUtils.isEmpty(winningRecordDOList)){
            logger.info("中奖列表为空，不用邮箱通知");
            return;
        }
        for(WinningRecordDO winningRecordDO : winningRecordDOList) {
            //恭喜${name}在${winningTime}的${activityName}活动中荣获${prizeTiers}，奖品为${prizeName}。
            String context = "恭喜" + winningRecordDO.getWinnerName() + "在"
                    + DateUtil.formatTime(winningRecordDO.getWinningTime()) + "举办的"
                    + winningRecordDO.getActivityName() + "活动中荣获"
                    + ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage()
                    + "奖品为：" + winningRecordDO.getPrizeName()+"。请及时领取。";

            mailUtil.sendSampleMail(winningRecordDO.getWinnerEmail(),
                    "中奖通知",
                    context);
        }

    }

    /**
     * 发短信
     * @param winningRecordDOList
     */
    private void sendMessage(List<WinningRecordDO> winningRecordDOList) {
        if(CollectionUtils.isEmpty(winningRecordDOList)){
            logger.info("中奖列表为空，不用短信通知");
            return;
        }
        for (WinningRecordDO winningRecordDO : winningRecordDOList) {
            Map<String, String> map = new HashMap<>();
            map.put("name", winningRecordDO.getWinnerName());
            map.put("activityName", winningRecordDO.getActivityName());
            map.put("prizeTiers", ActivityPrizeTiersEnum.forName
                    (winningRecordDO.getPrizeTier()).getMessage());
            map.put("prizeName", winningRecordDO.getPrizeName());
            map.put("winningTime", DateUtil.formatTime(winningRecordDO.getWinningTime()));
            smsUtil.sendMessage("SMS_478960145",
                    winningRecordDO.getWinnerPhoneNumber().getValue(),
                    JacksonUtil.writeValueAsString(map));
        }
    }

    /**
     * 状态扭转
     * @param param
     */
    private void statusConvert(DrawPrizeParam param) {
        ConvertActivityStatusDTO dto = new ConvertActivityStatusDTO();
        dto.setActicityId(param.getActivityId());
        dto.setTargetActivityStatus(ActivityStatusEnum.COMPLETED);
        dto.setPrizeId(param.getPrizeId());
        dto.setTargetPrizeStatus(ActivityPrizeStatusEnum.COMPLETED);
        dto.setUserIds(param.getWinnerList().stream()
                .map(DrawPrizeParam.Winner::getUserId)
                .collect(Collectors.toList())
        );
        dto.setTargetUserStatus(ActivityUserStatusEnum.COMPLETED);

        activityStatusManager.handlerEvent(dto);
    }

}
